"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from threading import Thread
from msnp.persistence import PersistenceController, XMLController
from msnp.service.network import sending 
from msnp.service import network, utils
from msnp.logger import Logger
from msnp.service.utils import Hash
from msnp.service.network import logic
from msnp.model.PTPModel import BackupContent, UserGroup
from msnp.model import PTPModel, MediaModel
from msnp.service.network.model import Model
from msnp.service.network.model.CustodyModel import MemberStatusChangedMessage
from msnp.service import synch
from msnp.gui import MySignal
from msnp.model.SettingsModel import GroupSettings
from msnp.model.MediaModel import NetSynchChanges

def groupsReceived(groups, reactType, searchFinished):
    """Depending on the 'reactType' this function displays the group in the GroupSearchWidget,
    displays all group details in the GroupDetailsWidget or sends responsibility searches for its members.
    
    Args:
        groups: a list of Group instances.
        reactType: a string, defined in CallbackReactType.
        searchFinished: a boolean that determines whether the search is finished.
    """
    
    if reactType == Model.CallbackReactType.DISPLAYGROUP:
        __displayGroups(groups, searchFinished)
    elif reactType == Model.CallbackReactType.DISPLAYGROUPDETAILS:
        __displayGroupDetails(groups[0])
    else:
        if groups != None and groups[0] != None and groups[0].getMembers() != None and len(groups[0].getMembers()) > 0:
            myIdentity = network.NetworkController.getMySelf().getMyIdentity()
            for member in groups[0].getMembersByStatus(PTPModel.MemberStatus.ACTIVE):
                if member.getName() != myIdentity.getName():
                    if reactType == Model.CallbackReactType.SEARCHMEMBER_SENDMEDIA:
                        __sendMedia(member, groups[0])
                    elif reactType == Model.CallbackReactType.SEARCHMEMBER_INITSYNCH:
                        __initSynch(member, groups[0])


def __displayGroups(groups, searchFinished):
    """This function triggers the graphical user interface to display groups in the group search widget.
    
    Args:
        groups: a list of Group instances.
        searchFinished: a boolean that determines whether the search is finished.
    """
    
    if groups != None and len(groups) > 0:
        for group in groups:
            Logger.info("received group " + group.getGroupname() + " (owner=" + group.getOwnername())
            MySignal.emitSearchGroupReceivedSignal(group)
    if searchFinished:
        MySignal.emitSearchGroupFinishedSignal()


def __displayGroupDetails(group):
    """This function triggers the graphical user interface to display group details.
    
    Args:
        group: the Group instance.
    """
    
    MySignal.emitSearchGroupDetailsFinishedSignal(group)


def __sendMedia(member, group):
    """This functions searches the PeerInfo instances of a group's member.
    
    Args:
        group: the Group instance.
        member: the Member instance.
    """
    
    if synch.NetSynchController.doSynchWithGroup(group, True):
        myIdentity = network.NetworkController.getMySelf().getMyIdentity()
        synch.NetSynchController.addGroupForUsername(member.getName(), group, member.getTimestamp())
        logic.ResponsibilityLogic.findResponsibleIdentity(member.getName(), myIdentity.getName(), myIdentity, Model.CallbackReactType.SEARCHMEMBER_SENDMEDIA)
    
    
def __initSynch(member, group):
    """This function initializes the media synch.
    
    Args:
        group: the Group instance.
        member: the Member instance.
    """
    
    Logger.info("got group to retrieve media content since last synch")
    if synch.NetSynchController.doSynchWithGroup(group, False):
        myIdentity = network.NetworkController.getMySelf().getMyIdentity()
        groupname = group.getGroupname()
        ownername = group.getOwnername()
        timestamp = PersistenceController.selectLastUpdateProcessedTimestamp(groupname, ownername)
        if timestamp > -1:
            if network.NetworkController.getMySelf().isLowLevelPeer():
                respIdentity = network.NetworkController.getMySelf().getResponsibleSuperPeer()
            else:
                respIdentity = myIdentity
            Logger.info("poll media content from group " + groupname + " (" + ownername + ") from member " + member.getName())
            sending.Sender.pollMediaContentSinceLastSynch(respIdentity, myIdentity.getName(), member.getName(), group, timestamp)
        else:
            Logger.info("no media content to retrieve: no last update timestamp")
    
    
def memberStatusChanged(groupname, ownername, status, membername, processed=False):
    """This functions processes a member status change message.
    
    Depending on the membership status it adds or removes the group from the settings.
    
    Args:
        groupname: the group name (string)
        ownername: the owner name of the group (string)
        status: the status defined in MemberStatus
        membername: the member name (string)
        processed: a boolean that determines whether the status change is already processed
    """
    
    if processed == False:
        Logger.info("process member status changed msg")
        myName = network.NetworkController.getMySelf().getMyIdentity().getName()
        if myName == membername:
            if status != None and status != PTPModel.MemberStatus.NOT_CONFIRMED:
                settings = XMLController.readAllSettings()
                groupSettings = settings.getCurrentUserSettings().getGroupSettingsList()
                if status == PTPModel.MemberStatus.ACTIVE:
                    # insert a net synch change so that we know from when on to poll data when starting application
                    netSynchChange = NetSynchChanges(None, groupname, ownername, None, None, True, MediaModel.NetSynchChangeSource.INITIAL)
                    PersistenceController.insertNetSynchChange(netSynchChange)
                    groupSettings.append(GroupSettings(groupname, ownername, True, True))
                elif status == PTPModel.MemberStatus.BLOCKED or status == PTPModel.MemberStatus.DELETED:
                    for gs in groupSettings:
                        if gs.getGroupname() == groupname and gs.getOwnername() == ownername:
                            groupSettings.remove(gs)
                            break
                XMLController.storeSettings(settings)
    else:
        Logger.info("have already processed member status changed message")
    MySignal.emitMsgReceivedSignal()


##########################
## execute remote calls ##
##########################

def getGroupsWithOwner(originalRespIdentity, originalName, ownername, groupname, displayname, genre, reactType):
    """Searches through the own groups for groups that corresponds to the given parameters ownername, groupname, displayname and genre.
    
    If groups are found they get are send to the original requestor.
    If this peer is responsible but there exists no group with the given parameters it callbacks to the original requestor.
    Only if this peer is not responsible for the group it forwards the call to the nearest identity of the owner name.
    
    Args:
        originalRespIdentity: the original identity (PeerInfo instance)
        originalName: the name of the original identity (string)
        ownername: the owner name (string)
        groupname: the group name (string)
        displayname: the display name (string)
        genre: the genre (string)
        reactType: determines how to react
    """
    
    groups = PersistenceController.selectGroupsForOwnername(ownername)
    ownerHashedNameLong = long(Hash.hash(ownername), 16)
    if groups != None and len(groups) > 0 and groups[0].hasDetails():
        groupsToCallback = []
        for group in groups:
            if group.compareValues(groupname, displayname, genre):
                groupsToCallback.append(group)
        if groupsToCallback != None and len(groupsToCallback) > 0:
            Logger.info("callbacking groups")
            if originalRespIdentity.getName() == network.NetworkController.getMySelf().getMyIdentity().getName():
                executeCallbackGroups(originalName, groupsToCallback, reactType, True)
            else:
                sending.Sender.callbackGroups(originalRespIdentity, originalName, groupsToCallback, reactType, True)
            return
    if logic.ResponsibilityLogic.amIResponsibleIdentityFor(ownerHashedNameLong):
        Logger.info("searched group does not exist")
        if originalRespIdentity.getName() == network.NetworkController.getMySelf().getMyIdentity().getName():
            executeCallbackGroups(originalName, None, reactType, True)
        else:
            sending.Sender.callbackGroups(originalRespIdentity, originalName, None, reactType, True)
    else:
        sending.Sender.getGroupsWithOwner(originalRespIdentity, originalName, ownername, groupname, displayname, genre, reactType)


def getGroupsWithoutOwner(originalRespIdentity, originalName, alreadyReceivedUsers, groupname, displayname, genre, reactType):
    """Searches through the own groups for groups that corresponds to the given parameters groupname, displayname and genre.
    
    The owner name is not given and the other parameters can also be None (are ignored on comparing).
    If groups are found they are callbacked to the original requestor.
    If this peer is not already in the list of already received users it forwards the call sequentially.
    
    Args:
        originalRespIdentity: the original identity (PeerInfo instance)
        originalName: the name of the original identity (string)
        alreadyReceivedUsers: a list that contains all peers that already received the message
        groupname: the group name (string)
        displayname: the display name (string)
        genre: the genre (string)
        reactType: determines how to react
    """
    
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    searchFinished = False
    groupsToCallback = []
    if alreadyReceivedUsers.__contains__(myName) == False:
        if network.NetworkController.getMySelf().getSuccessor() != None:
            print "forward search"
            alreadyReceivedUsers.append(myName)
            sending.Sender.getGroupsWithoutOwner(originalRespIdentity, originalName, alreadyReceivedUsers, groupname, displayname, genre, reactType)
        else:
            print "no successor"
            searchFinished = True
        
        groups = PersistenceController.selectAllGroups()
        if groups != None and len(groups) > 0:
            for group in groups:
                if group.compareValues(groupname, displayname, genre):
                    groupsToCallback.append(group)
    else:
        Logger.info("Search finished")
        searchFinished = True
    
    if searchFinished or (groupsToCallback != None and len(groupsToCallback) > 0):
        Logger.info("callbacking groups")
        if groupsToCallback != None:
            Logger.info(str(len(groupsToCallback)) + " groups")
        if originalRespIdentity.getName() == myName:
            executeCallbackGroups(originalName, groupsToCallback, reactType, searchFinished)
        else:
            sending.Sender.callbackGroups(originalRespIdentity, originalName, groupsToCallback, reactType, searchFinished)

def callbackGroups(originalName, groups, reactType, searchFinished):
    """Callback function for the group search.
    
    If the original requestor is the current peer it calls the groupsReceived function.
    If the original requestor is one of the online low level peers it adds a group received message to its custody queue.
    If the original requestor is offline it does nothing.
    If this peer is not responsible for the original requestor it forwards the callback to the nearest identity.
    
    Args:
        originalName: the name of the original identity (string)
        groups: a list of Group instances
        reactType: determines how to react
        searchFinished: a boolean that determines whether the search procedure is already finished.
    """
    
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    if originalName == myName:
        groupsReceived(groups, reactType, searchFinished)
    else:
        lowLevelPeer = network.NetworkController.getMySelf().getLowLevelPeerByName(originalName)
        if lowLevelPeer != None:
            Logger.info("Groups received for my low level peer " + originalName)
            network.CustodyQueue.addGroupsReceivedMessage(originalName, groups, reactType, searchFinished)
        else:
            hashedNameLong = long(Hash.hash(originalName), 16)
            if logic.ResponsibilityLogic.amIResponsibleIdentityFor(hashedNameLong):
                Logger.info("Groups received for a low level peer that is now offline")
            else:
                Logger.info("Groups received for a peer that is no more my low level peer: " + originalName + ". I forward message...")
                nearestIdentity = network.NetworkController.getMySelf().getNearestIdentity(hashedNameLong)[0]
                sending.Sender.callbackGroups(nearestIdentity, originalName, groups, reactType, searchFinished)

    
def establishGroup(group):
    """Inserts the given group into the database and updates all backups.
    
    Args:
        group: the Group instance
        
    Returns:
        Returns the group and a return value whether the establishment was successful or not.
    """
    
    isInserted = PersistenceController.establishGroup(group)
    if isInserted == PTPModel.ReturnType.SUCCESSFUL:
        # update fingers if insertion was successful
        group = __updateGroupAtFingers(group.getGroupname(), group.getOwnername(), group.getOwnername())
    else:
        group = None
    return (group, isInserted)


def changeGroupDetails(group):
    """Changes the details for the given group.
    
    The group has to exists, otherwise an error occurrs during the update.
    If the update was successful the backups are updated.
    
    Args:
        group: the Group instance
        
    Returns:
        Returns whether the update was successful or not.
    """
    
    isUpdated = PersistenceController.updateGroup(group)
    if isUpdated == PTPModel.ReturnType.SUCCESSFUL:
        # update fingers if insertion was successful
        __updateGroupAtFingers(group.getGroupname(), group.getOwnername(), None)
    return isUpdated


def addMember(ownername, membername, groupname, originalIdentity):
    """Adds a new member to the given group.
    
    If the current peer is not responsible for the group it forwards the call to the nearest identity.
    If the current peer is responsible, the group exists, the membership does not exist and the group is active it inserts the new membership into the database.
    Afterwards it callbacks the status change to both the member and the owner of the group.
    If the insertion was successful the backups will be updated.
    
    Args:
        ownername: the owner name (string)
        membername: the member name (string)
        groupname: the group name (string)
        originalIdentity: the original identity (PeerInfo instance)
    """
    
    ownerHashedNameLong = long(Hash.hash(ownername), 16)
    network.NetworkLock.acquireJoinLock()
    network.NetworkLock.acquireBackupLock()
    network.LeaveRequest.block()
    try:
        if logic.ResponsibilityLogic.amIResponsibleIdentityFor(ownerHashedNameLong):
            status = PTPModel.MemberStatus.NOT_CONFIRMED
            result = PTPModel.MemberStatusChangedReturnType.ERROR
            group = PersistenceController.selectGroupForGroupnameAndOwnername(groupname, ownername)
            if group != None:
                timestamp = utils.DateFormatter.formatTimeAsFloat()
                if group.containsMember(membername) == False:
                    if group.isActive() == False:
                        result = PTPModel.MemberStatusChangedReturnType.GROUP_IS_INACTIVE
                    else:
                        result = PersistenceController.insertMemberGroup(membername, groupname, status, timestamp, ownername, False)
                elif group.getMember(membername).getStatus() == PTPModel.MemberStatus.DELETED:
                    result = PersistenceController.updateMemberGroup(membername, groupname, ownername, status, timestamp)
                else:
                    result = PTPModel.MemberStatusChangedReturnType.ALREADY_MEMBER
            else:
                result = PTPModel.MemberStatusChangedReturnType.GROUP_NOT_EXISTING
                
            myName = network.NetworkController.getMySelf().getMyIdentity().getName()
            # update fingers if insertion was successful
            if result == PTPModel.MemberStatusChangedReturnType.SUCCESSFUL:
                __updateGroupAtFingers(groupname, ownername, None)
            else:
                status = None
            # send callback to member
            Logger.info("callback member status change to " + originalIdentity.getName() + " (interested is " + membername + ")")
            if originalIdentity.getName() == myName:
                executeCallbackMemberStatusChanged(membername, membername, groupname, ownername, result, status)
            else:
                sending.Sender.callbackMemberStatusChanged(originalIdentity, membername, membername, groupname, ownername, result, status)
            # send callback to owner if it is not the same as the member
            if membername != ownername:
                Logger.info("callback member status change to myself (interested is " + ownername + ")")
                executeCallbackMemberStatusChanged(ownername, membername, groupname, ownername, result, status)
        else:
            Logger.info("forward addmember")
            sending.Sender.addMember(ownername, membername, groupname, originalIdentity)
    finally:
        network.NetworkLock.releaseJoinLock()
        network.NetworkLock.releaseBackupLock()
        network.LeaveRequest.release()
        

def setMemberStatus(originalRespIdentity, originalName, membername, groupname, ownername, status):
    """This function changes the membership status.
    
    If the current peer is not responsible for the group it forwards the call.
    If the group and the membership exist, the group is active and the status change is allowed it updates the membership.
    If the change of membership was successful all backups will be updated.
    The owner and the member are informed using a callback.
    
    Args:
        originalRespIdentity: the original identity (PeerInfo instance)
        originalName: the name of the original identity (string)
        membername: the member name (string)
        groupname: the group name (string)
        ownername: the owner name (string)
        status: the membership status (defined in MemberStatus)
    """

    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    network.NetworkLock.acquireJoinLock()
    network.NetworkLock.acquireBackupLock()
    network.LeaveRequest.block()
    try:
        if PersistenceController.selectUserImResponsibleForUsername(ownername) != None:
            group = PersistenceController.selectGroupForGroupnameAndOwnername(groupname, ownername)
            if group == None:
                Logger.info("Error! Cannot change member status: group not existing")
                result = PTPModel.MemberStatusChangedReturnType.GROUP_NOT_EXISTING
                status = None
            elif group.containsMember(membername) == False:
                Logger.info("Error! Cannot change member status: member in group not existing")
                result = PTPModel.MemberStatusChangedReturnType.IS_NOT_MEMBER
                status = None
            elif group.isActive() == False:
                Logger.info("Error! Cannot change member status: group is inactive")
                result = PTPModel.MemberStatusChangedReturnType.GROUP_IS_INACTIVE
                status = None
            else:
                allowed = False
                statusInDb = group.getMember(membername).getStatus()
                # user can change its own state from any state to DELETED if it is not the owner
                if membername == originalName and group.getOwnername() != membername:
                    if status == PTPModel.MemberStatus.DELETED:
                        allowed = True
                # owner can change the state of a member of its group from any status except DELETED to ACTIVE or BLOCKED
                if group.getOwnername() == originalName and group.getOwnername() != membername:
                    if status == PTPModel.MemberStatus.ACTIVE or status == PTPModel.MemberStatus.BLOCKED:
                        if statusInDb != PTPModel.MemberStatus.DELETED:
                            allowed = True
                            
                if allowed == False:
                    Logger.info("Error! Cannot change member status: change not allowed")
                    result = PTPModel.MemberStatusChangedReturnType.STATUSCHANGE_NOT_ALLOWED
                    status = None
                else:
                    timestamp = utils.DateFormatter.formatTimeAsFloat()
                    result = PersistenceController.updateMemberGroup(membername, groupname, ownername, status, timestamp)
                    if result == PTPModel.MemberStatusChangedReturnType.SUCCESSFUL:
                        Logger.info("Member status changed")
                        # update fingers if update was successful
                        __updateGroupAtFingers(groupname, ownername, None)
                    else:
                        Logger.info("Error! Cannot change member status: error while updating own db: " + result)
                        status = None
            
            Logger.info("callback status change to " + originalRespIdentity.getName() + " (interested is " + originalName + ")")
            if originalRespIdentity.getName() == myName:
                executeCallbackMemberStatusChanged(originalName, membername, groupname, ownername, result, status)
            else:
                sending.Sender.callbackMemberStatusChanged(originalRespIdentity, originalName, membername, groupname, ownername, result, status)
            
            # originalname is either member or owner, so if no error occurred inform other one
            if status != None:
                interestedName = None
                if originalName != membername:
                    interestedName = membername
                if originalName != ownername:
                    interestedName = ownername
                if interestedName != None:
                    hashedNameLong = long(Hash.hash(interestedName), 16)
                    memberRespIdentity = network.NetworkController.getMySelf().getNearestIdentity(hashedNameLong)[0]
                    Logger.info("callback status change to " + memberRespIdentity.getName() + " (interested is " + interestedName + ")")
                    sending.Sender.callbackMemberStatusChanged(memberRespIdentity, interestedName, membername, groupname, ownername, result, status)
        else:
            sending.Sender.setMemberStatus(originalRespIdentity, originalName, membername, groupname, ownername, status)
    finally:
        network.NetworkLock.releaseJoinLock()
        network.NetworkLock.releaseBackupLock()
        network.LeaveRequest.release()


def callbackMemberStatusChanged(interestedName, membername, groupname, ownername, result, status):
    """Callback function for a membership status change.
    
    If the current peer is not responsible it forwards the call to the nearest identity of 'interestedName'.
    If the current peer is responsible and the given result is successful:
    - it inserts the user-group information into its database. If the insertion was successful it updates the backups.
    - it creates a member status changed message and insert it in its database. If the insertion was successful it updates the backups.
    - if this peers is the interested peer it calls the memberStatusChanged function.
    - if the interested peer is a online low level peer it inserts the message into its custody queue.
    
    Args:
        interestedName: or the member name, or the owner name (string)
        groupname: the group name (string)
        ownername: the owner name (string)
        result: the result (defined in MemberStatusChangedReturnType)
        status: the membership status (defined in MemberStatus)
    """
    
    interestedHashedNameLong = long(Hash.hash(interestedName), 16)
    network.NetworkLock.acquireJoinLock()
    network.NetworkLock.acquireBackupLock()
    network.LeaveRequest.block()
    try:
        if logic.ResponsibilityLogic.amIResponsibleIdentityFor(interestedHashedNameLong):
            if PersistenceController.selectUserImResponsibleForUsername(interestedName) != None:
                myName = network.NetworkController.getMySelf().getMyIdentity().getName()
                if status != None:
                    # insert change into db
                    Logger.info("Status of " + membername + " in group " + groupname + " (owner " + ownername + "): " + status)
                    memberHashedNameLong = long(Hash.hash(membername), 16)
                    if logic.ResponsibilityLogic.amIResponsibleIdentityFor(memberHashedNameLong):
                        if PersistenceController.selectUserGroup(membername, groupname, ownername) == None:
                            if PersistenceController.insertUserGroupAndGroup(membername, groupname, ownername):
                                __updateGroupAtFingers(groupname, ownername, membername)
                            else:
                                result = PTPModel.MemberStatusChangedReturnType.ERROR
                                status = None
                if status == None:
                    Logger.info("Error while updating memberstatus of " + membername + " in group " + groupname + " (owner " + ownername + "): " + result)
                
                if interestedName == myName:
                    memberStatusChanged(groupname, ownername, status, membername)
                    processed = True
                else:
                    # add to queue if its one of my online LLPs so that he knows he has a message
                    lowLevelPeer = network.NetworkController.getMySelf().getLowLevelPeerByName(interestedName)
                    if lowLevelPeer != None:
                        Logger.info("got member status changed msg for " + interestedName + ", put in custody queue")
                        network.CustodyQueue.addMemberStatusChangedMessage(interestedName, membername, groupname, ownername, result, status)
                    else:
                        Logger.info("got member status changed msg for offline peer")
                    processed = False
                
                # store message in db to retrieve it later
                msg = MemberStatusChangedMessage(interestedName, membername, groupname, ownername, result, status, processed)
                PersistenceController.insertMessages(interestedName, [msg])
                user = PersistenceController.selectUserImResponsibleForUsername(interestedName)
                backupContent = BackupContent(myName, [user], None, None, PTPModel.BackupType.UPDATE)
                network.BackupController.updateBackupAtFingers(backupContent)
            else:
                Logger.info("Got callback of member status change, but user " + interestedName + " does not exist")
        else:
            nearestIdentity = network.NetworkController.getMySelf().getNearestIdentity(interestedHashedNameLong)[0]
            Logger.info("forward callback member status changed to " + nearestIdentity.getName())
            sending.Sender.callbackMemberStatusChanged(nearestIdentity, interestedName, membername, groupname, ownername, result, status)
    finally:
        network.NetworkLock.releaseJoinLock()
        network.NetworkLock.releaseBackupLock()
        network.LeaveRequest.release()


def getGroupsWithMembersToConfirmForLowLevel(llName):
    """This function forwards the call to the PersistenceController module.
    
    Args:
        llName: the name of the low level peer (string)
        
    Returns:
        Returns a list of Group instances.
    """
    
    return PersistenceController.selectGroupsWithMembersToConfirm(llName)


def getStoredMessagesForUsername(llName):
    """Selects all stored messages for a given low level peer.
    
    Args:
        llName: the name of the low level peer (string)
        
    Returns:
        Returns a list of MemberStatusChangedMessage instances.
    """
    
    messages = PersistenceController.selectMessagesForUsername(llName, True)
    if messages == None:
        return []
    __updateUserAtFingers(llName)
    return messages


def getGroupsForUsername(llName):
    """Selects all groups where the given low level peer is a member of.
    
    Args:
        llName: the name of the low level peer (string)
        
    Returns:
        Returns a list of Group instances.
    """
    
    return PersistenceController.selectGroupsForUsername(llName)


def getGroupsAndMessageCountForUsername(llName):
    """Selects all groups where the given low level peer is a member of together with the number of already processed stored messages.
    
    Args:
        llName: the name of the low level peer (string)
        
    Returns:
        Returns only already processed messages because the not processed once get put into the custody queue
        and the low level peer will process them.
    """
    
    messages = PersistenceController.selectMessagesForUsername(llName, False)
    msgCount = 0
    if messages != None:
        msgCount = len(messages)
    groups = PersistenceController.selectGroupsForUsername(llName)
    Logger.info("i have " + str(len(groups)) + " groups and " + str(msgCount) + " msg for peer " + llName)
    return (groups, msgCount)


def __updateGroupAtFingers(groupname, ownername, membername):
    """Updates the backup at the fingers.
    
    The backup contains the given group and eventually the user group relation of the given member.
    
    Args:
        groupname: the group name (string)
        ownername: the owner name (string)
        membername: the member name (string)
        
    Returns:
        Returns a Group instance.
    """
    
    group = PersistenceController.selectGroupForGroupnameAndOwnername(groupname, ownername)
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    userGroup = None
    if membername != None and len(membername) > 0:
        userGroup = [UserGroup(membername, group.getGroupname(), group.getOwnername())]
    backupContent = BackupContent(myName, None, userGroup, [group], PTPModel.BackupType.UPDATE)
    network.BackupController.updateBackupAtFingers(backupContent)
    return group


def __updateUserAtFingers(username):
    """Updates the backup at the fingers.
    
    The backup contains the given user.
    
    Args:
        username: the user name (string)
    """
    
    user = PersistenceController.selectUserImResponsibleForUsername(username)
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    backupContent = BackupContent(myName, [user], None, None, PTPModel.BackupType.UPDATE)
    network.BackupController.updateBackupAtFingers(backupContent)
    

##############################
## threads for remote calls ##
##############################

class GetGroupsWithOwnerThread(Thread):
    """Group search thread with given ownername.

    Attributes:
        __originalRespIdentity: The original identity (PeerInfo instance)
        __originalName: the name of the original identity (string)
        __ownername: the owner name (string)
        __groupname: the group name (string)
        __displayname: the display name (string)
        __genre: the genre (string)
        __reactType: determines how to react on the execution.
    """
    
    def __init__(self, originalRespIdentity, originalName, ownername, groupname, displayname, genre, reactType):
        """the init method"""
        Thread.__init__(self)
        self.__originalRespIdentity = originalRespIdentity
        self.__originalName = originalName
        self.__ownername = ownername
        self.__groupname = groupname
        self.__displayname = displayname
        self.__genre = genre
        self.__reactType = reactType
    
    def run(self):
        """This method forwards the call to the getGroupsWithOwner function."""
        getGroupsWithOwner(self.__originalRespIdentity, self.__originalName, self.__ownername, self.__groupname, self.__displayname, self.__genre, self.__reactType)


class GetGroupsWithoutOwnerThread(Thread):
    """Group search thread without given ownername.

    Attributes:
        __originalRespIdentity: The original identity (PeerInfo instance)
        __originalName: the name of the original identity (string)
        __alreadyReceivedUsers: a list containing all peers that already received the message
        __groupname: the group name (string)
        __displayname: the display name (string)
        __genre: the genre (string)
        __reactType: determines how to react on the execution.
    """
    
    def __init__(self, originalRespIdentity, originalName, alreadyReceivedUsers, groupname, displayname, genre, reactType):
        """the init method"""
        Thread.__init__(self)
        self.__originalRespIdentity = originalRespIdentity
        self.__originalName = originalName
        self.__alreadyReceivedUsers = alreadyReceivedUsers
        self.__groupname = groupname
        self.__displayname = displayname
        self.__genre = genre
        self.__reactType = reactType
    
    def run(self):
        """This method forwards the call to the getGroupsWithoutOwner function."""
        getGroupsWithoutOwner(self.__originalRespIdentity, self.__originalName, self.__alreadyReceivedUsers, self.__groupname, self.__displayname, self.__genre, self.__reactType)


class CallbackGroupsThread(Thread):
    """Thread for group callbacks.

    Attributes:
        __originalName: the name of the original identity (string)
        __groups: a list containing Group instances
        __reactType: determines how to react on the execution.
        __searchFinished: a boolean that determines whether the search procedure is finished.
    """

    def __init__(self, originalName, groups, reactType, searchFinished):
        """the init method"""
        Thread.__init__(self)
        self.__originalName = originalName
        self.__groups = groups
        self.__reactType = reactType
        self.__searchFinished = searchFinished
        
    def run(self):
        """This method forwards the call to the callbackGroups function."""
        callbackGroups(self.__originalName, self.__groups, self.__reactType, self.__searchFinished)


class AddMember(Thread):
    """Thread for adding a member to a group.

    Attributes:
        __ownername: the owner name (string).
        __membername: the member name (string).
        __groupname: the group name (string).
        __originalIdentity: the PeerInfo instance.
    """
    
    def __init__(self, ownername, membername, groupname, originalIdentity):
        """the init method"""
        Thread.__init__(self)
        self.__ownername = ownername 
        self.__membername = membername
        self.__groupname = groupname
        self.__originalIdentity = originalIdentity
        
    def run(self):
        """This method forwards the call to the addMember function."""
        addMember(self.__ownername, self.__membername, self.__groupname, self.__originalIdentity)


class SetMemberStatusThread(Thread):
    """Thread for membership status changes.

    Attributes:
        __respIdentity: the responsible identity (PeerInfo instance).
        __originalName: the name or the original identity (string).
        __membername: the member name (string).
        __groupname: the group name (string).
        __ownername the owner name (string).
        __status: the status
    """
    
    def __init__(self, respIdentity, originalName, membername, groupname, ownername, status):
        """the init method"""
        Thread.__init__(self)
        self.__respIdentity = respIdentity
        self.__originalName = originalName
        self.__membername = membername
        self.__groupname = groupname
        self.__ownername = ownername
        self.__status = status
    
    def run(self):
        """This method forwards the call to the setMemberStatus function."""
        setMemberStatus(self.__respIdentity, self.__originalName, self.__membername, self.__groupname, self.__ownername, self.__status)


class CallbackMemberStatusChangedThread(Thread):
    """Thread for membership status change callbacks.

    Attributes:
        __interestedName: or the member name, or the owner name (string).
        __membername: the member name (string).
        __groupname: the group name (string).
        __ownername the owner name (string).
        __result: the result
        __status: the status
    """
    
    def __init__(self, interestedName, membername, groupname, ownername, result, status):
        """the init method"""
        Thread.__init__(self)
        self.__interestedName = interestedName
        self.__membername = membername
        self.__groupname = groupname
        self.__ownername = ownername
        self.__result = result
        self.__status = status
        
    def run(self):
        """This method forwards the call to the callbackMemberStatusChanged function."""
        callbackMemberStatusChanged(self.__interestedName, self.__membername, self.__groupname, self.__ownername, self.__result, self.__status)
        
class GetStoredMessagesThread(Thread):
    """Thread for retrieving all stored messages of a peer.
    """
    
    def __init__(self):
        """the init method"""
        Thread.__init__(self)
    
    def run(self):
        """Calls either the getStoredMessagesForUsername function or the remote function at the responsible super peer.
        Emits a message reiceived signal."""
        myName = network.NetworkController.getMySelf().getMyIdentity().getName()
        if network.NetworkController.getMySelf().isLowLevelPeer():
            messages = sending.Sender.getStoredMessagesForLowLevel(myName)
        else:
            messages = getStoredMessagesForUsername(myName)
        MySignal.emitMsgReceivedSignal(messages)
        
            
#####################
## execute threads ##
#####################

def executeGetGroupsWithOwner(originalRespIdentity, originalName, ownername, groupname, displayname, genre, reactType):
    """This function starts a group search thread with given ownername.

    Args:
        originalRespIdentity: The original identity (PeerInfo instance)
        originalName: the name of the original identity (string)
        ownername: the owner name (string)
        groupname: the group name (string)
        displayname: the display name (string)
        genre: the genre (string)
        reactType: determines how to react on the execution.
    """
    
    getGroupsThread = GetGroupsWithOwnerThread(originalRespIdentity, originalName, ownername, groupname, displayname, genre, reactType)
    getGroupsThread.start()


def executeGetGroupsWithoutOwner(originalRespIdentity, originalName, alreadyReceivedUsers, groupname, displayname, genre, reactType):
    """This function starts a group search thread without given ownername.

    Args:
        originalRespIdentity: The original identity (PeerInfo instance)
        originalName: the name of the original identity (string)
        alreadyReceivedUsers: a list containing all peers that already received the message
        groupname: the group name (string)
        displayname: the display name (string)
        genre: the genre (string)
        reactType: determines how to react on the execution.
    """
    
    getGroupsThread = GetGroupsWithoutOwnerThread(originalRespIdentity, originalName, alreadyReceivedUsers, groupname, displayname, genre, reactType)
    getGroupsThread.start()


def executeCallbackGroups(originalName, groups, reactType, searchFinished):
    """This function starts a thread for group callbacks.

    Args:
        originalName: the name of the original identity (string)
        groups: a list containing Group instances
        reactType: determines how to react on the execution.
        searchFinished: a boolean that determines whether the search procedure is finished.
    """
    
    callbackThread = CallbackGroupsThread(originalName, groups, reactType, searchFinished)
    callbackThread.start()


def executeAddMember(ownername, membername, groupname, originalIdentity):
    """This function starts a thread for adding a member to a group.

    Args:
        ownername: the owner name (string).
        membername: the member name (string).
        groupname: the group name (string).
        originalIdentity: the PeerInfo instance.
    """
    
    addMemberThread = AddMember(ownername, membername, groupname, originalIdentity)
    addMemberThread.start()
    
    
def executeSetMemberStatus(respIdentity, originalName, membername, groupname, ownername, status):
    """This function starts a thread for membership status changes.

    Args:
        respIdentity: the responsible identity (PeerInfo instance).
        originalName: the name or the original identity (string).
        membername: the member name (string).
        groupname: the group name (string).
        ownername the owner name (string).
        status: the status
    """
    
    setMemberStatusThread = SetMemberStatusThread(respIdentity, originalName, membername, groupname, ownername, status)
    setMemberStatusThread.start()


def executeCallbackMemberStatusChanged(interestedName, membername, groupname, ownername, result, status):
    """this function starts a thread for membership status change callbacks.

    Args:
        interestedName: or the member name, or the owner name (string).
        membername: the member name (string).
        groupname: the group name (string).
        ownername the owner name (string).
        result: the result
        status: the status
    """
    
    callbackMemberStatusChangedThread = CallbackMemberStatusChangedThread(interestedName, membername, groupname, ownername, result, status)
    callbackMemberStatusChangedThread.start()


def executeGetStoredMessages():
    """This function starts a thread for retrieving all stored messages of a peer.
    """
    
    getMessagesThread = GetStoredMessagesThread()
    getMessagesThread.start()
